import os
import sys
import subprocess
import urllib
try:
    import select
except ImportError:
    select = None
from paste.util import converters
__all__ = ['CGIError', 'CGIApplication']

class CGIError(Exception):


class CGIApplication(object):

    def __init__(self, global_conf, script, path = None, include_os_environ = True, query_string = None):
        if global_conf:
            raise NotImplemented('global_conf is no longer supported for CGIApplication (use make_cgi_application); please pass None instead')
        self.script_filename = script
        if (path is None):
            path = os.environ.get('PATH', '').split(':')
        self.path = path
        if ('?' in script):
            (script, query_string,) = script.split('?', 1)
        if (os.path.abspath(script) != script):
            for path_dir in self.path:
                if os.path.exists(os.path.join(path_dir, script)):
                    self.script = os.path.join(path_dir, script)
                    break
            else:
                raise CGIError(('Script %r not found in path %r' % (script, self.path)))

        else:
            self.script = script
        self.include_os_environ = include_os_environ
        self.query_string = query_string



    def __call__(self, environ, start_response):
        if ('REQUEST_URI' not in environ):
            environ['REQUEST_URI'] = (urllib.quote(environ.get('SCRIPT_NAME', '')) + urllib.quote(environ.get('PATH_INFO', '')))
        if self.include_os_environ:
            cgi_environ = os.environ.copy()
        else:
            cgi_environ = {}
        for name in environ:
            if ((name.upper() == name) and isinstance(environ[name], str)):
                cgi_environ[name] = environ[name]

        if (self.query_string is not None):
            old = cgi_environ.get('QUERY_STRING', '')
            if old:
                old += '&'
            cgi_environ['QUERY_STRING'] = (old + self.query_string)
        cgi_environ['SCRIPT_FILENAME'] = self.script
        proc = subprocess.Popen([self.script], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=cgi_environ, cwd=os.path.dirname(self.script))
        writer = CGIWriter(environ, start_response)
        if (select and (sys.platform != 'win32')):
            proc_communicate(proc, stdin=StdinReader.from_environ(environ), stdout=writer, stderr=environ['wsgi.errors'])
        else:
            (stdout, stderr,) = proc.communicate(StdinReader.from_environ(environ).read())
            if stderr:
                environ['wsgi.errors'].write(stderr)
            writer.write(stdout)
        if not writer.headers_finished:
            start_response(writer.status, writer.headers)
        return []




class CGIWriter(object):

    def __init__(self, environ, start_response):
        self.environ = environ
        self.start_response = start_response
        self.status = '200 OK'
        self.headers = []
        self.headers_finished = False
        self.writer = None
        self.buffer = ''



    def write(self, data):
        if self.headers_finished:
            self.writer(data)
            return 
        self.buffer += data
        while ('\n' in self.buffer):
            if (('\r\n' in self.buffer) and (self.buffer.find('\r\n') < self.buffer.find('\n'))):
                (line1, self.buffer,) = self.buffer.split('\r\n', 1)
            else:
                (line1, self.buffer,) = self.buffer.split('\n', 1)
            if not line1:
                self.headers_finished = True
                self.writer = self.start_response(self.status, self.headers)
                self.writer(self.buffer)
                del self.buffer
                del self.headers
                del self.status
                break
            elif (':' not in line1):
                raise CGIError(('Bad header line: %r' % line1))
            else:
                (name, value,) = line1.split(':', 1)
                value = value.lstrip()
                name = name.strip()
                if (name.lower() == 'status'):
                    if (' ' not in value):
                        value = ('%s General' % value)
                    self.status = value
                else:
                    self.headers.append((name, value))





class StdinReader(object):

    def __init__(self, stdin, content_length):
        self.stdin = stdin
        self.content_length = content_length



    def from_environ(cls, environ):
        length = environ.get('CONTENT_LENGTH')
        if length:
            length = int(length)
        else:
            length = 0
        return cls(environ['wsgi.input'], length)


    from_environ = classmethod(from_environ)

    def read(self, size = None):
        if not self.content_length:
            return ''
        else:
            if (size is None):
                text = self.stdin.read(self.content_length)
            else:
                text = self.stdin.read(min(self.content_length, size))
            self.content_length -= len(text)
            return text




def proc_communicate(proc, stdin = None, stdout = None, stderr = None):
    read_set = []
    write_set = []
    input_buffer = ''
    trans_nl = (proc.universal_newlines and hasattr(open, 'newlines'))
    if proc.stdin:
        proc.stdin.flush()
        if input:
            write_set.append(proc.stdin)
        else:
            proc.stdin.close()
    if proc.stdout:
        read_set.append(proc.stdout)
    if proc.stderr:
        read_set.append(proc.stderr)
    while (read_set or write_set):
        (rlist, wlist, xlist,) = select.select(read_set, write_set, [])
        if (proc.stdin in wlist):
            (next, input_buffer,) = (input_buffer, '')
            next_len = (512 - len(next))
            if next_len:
                next += stdin.read(next_len)
            if not next:
                proc.stdin.close()
                write_set.remove(proc.stdin)
            else:
                bytes_written = os.write(proc.stdin.fileno(), next)
                if (bytes_written < len(next)):
                    input_buffer = next[bytes_written:]
        if (proc.stdout in rlist):
            data = os.read(proc.stdout.fileno(), 1024)
            if (data == ''):
                proc.stdout.close()
                read_set.remove(proc.stdout)
            if trans_nl:
                data = proc._translate_newlines(data)
            stdout.write(data)
        if (proc.stderr in rlist):
            data = os.read(proc.stderr.fileno(), 1024)
            if (data == ''):
                proc.stderr.close()
                read_set.remove(proc.stderr)
            if trans_nl:
                data = proc._translate_newlines(data)
            stderr.write(data)

    try:
        proc.wait()
    except OSError, e:
        if (e.errno != 10):
            raise 



def make_cgi_application(global_conf, script, path = None, include_os_environ = None, query_string = None):
    if (path is None):
        path = (global_conf.get('path') or global_conf.get('PATH'))
    include_os_environ = converters.asbool(include_os_environ)
    return CGIApplication(script, path=path, include_os_environ=include_os_environ, query_string=query_string)



